package advanced;

import JaCoP.constraints.Cumulative;
import JaCoP.constraints.Max;
import JaCoP.constraints.Min;
import JaCoP.constraints.Sum;
import JaCoP.constraints.XplusYeqZ;
import JaCoP.core.IntVar;
import JaCoP.core.Store;
import JaCoP.search.DepthFirstSearch;
import JaCoP.search.IndomainMax;
import JaCoP.search.Search;
import JaCoP.search.SelectChoicePoint;
import JaCoP.search.SimpleSelect;
import JaCoP.search.SmallestDomain;
import JaCoP.search.SmallestMax;

public class Concert {
	private static Store store = new Store();

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		long T1, T2, T;
		T1 = System.currentTimeMillis();
		schedulePlayers();
		T2 = System.currentTimeMillis();
		T = T2 - T1;
		System.out.println("\n\t*** Execution time = " + T + " ms");
	}

	private static void schedulePlayers() {
		System.out.println("Creating variables and imposing constraints...");

		/*
		 * Create pieces: origins, durations and calculated endings
		 */
		int[] durations = { 2, 4, 1, 3, 3, 2, 5, 7, 6 };
		int sumDurations = 0;
		for (int d : durations) {
			sumDurations += d;
		}
		IntVar piO[] = new IntVar[9];
		IntVar piD[] = new IntVar[9];
		IntVar piE[] = new IntVar[9];
		for (int i = 0; i < 9; i++) {
			piO[i] = new IntVar(store, "piece" + (i + 1), 0, sumDurations);
			piD[i] = new IntVar(store, "duration" + (i + 1), durations[i],
					durations[i]);
			piE[i] = new IntVar(store, "ending" + (i + 1), 0, sumDurations);
			store.impose(new XplusYeqZ(piO[i], piD[i], piE[i]));
		}

		/*
		 * Impose constraint that different pieces must come in a specified
		 * order
		 */
		IntVar one = new IntVar(store, "one", 1, 1);
		IntVar[] resources = { one, one, one, one, one, one, one, one, one };
		store.impose(new Cumulative(piO, piD, resources, one));

		/*
		 * Lock player to piece origins, related to which songs are played by
		 * each player
		 */
		IntVar[] pl1PiO = { piO[0], piO[1], piO[3], piO[5], piO[6], piO[8] };
		IntVar[] pl2PiO = { piO[0], piO[1], piO[3], piO[4], piO[5], piO[7] };
		IntVar[] pl3PiO = { piO[0], piO[1], piO[6], piO[7] };
		IntVar[] pl4PiO = { piO[0], piO[4], piO[5], piO[8] };
		IntVar[] pl5PiO = { piO[2], piO[4], piO[5], piO[6], piO[7] };

		IntVar[][] plPiO = { pl1PiO, pl2PiO, pl3PiO, pl4PiO, pl5PiO };

		/*
		 * Lock player to piece durations, related to which songs are played by
		 * each player
		 */
		IntVar[] pl1PiD = { piD[0], piD[1], piD[3], piD[5], piD[6], piD[8] };
		IntVar[] pl2PiD = { piD[0], piD[1], piD[3], piD[4], piD[5], piD[7] };
		IntVar[] pl3PiD = { piD[0], piD[1], piD[6], piD[7] };
		IntVar[] pl4PiD = { piD[0], piD[4], piD[5], piD[8] };
		IntVar[] pl5PiD = { piD[2], piD[4], piD[5], piD[6], piD[7] };

		IntVar[][] plPiD = { pl1PiD, pl2PiD, pl3PiD, pl4PiD, pl5PiD };

		/*
		 * Lock player to piece endings, related to which songs are played by
		 * each player
		 */
		IntVar[] pl1PiE = { piE[0], piE[1], piE[3], piE[5], piE[6], piE[8] };
		IntVar[] pl2PiE = { piE[0], piE[1], piE[3], piE[4], piE[5], piE[7] };
		IntVar[] pl3PiE = { piE[0], piE[1], piE[6], piE[7] };
		IntVar[] pl4PiE = { piE[0], piE[4], piE[5], piE[8] };
		IntVar[] pl5PiE = { piE[2], piE[4], piE[5], piE[6], piE[7] };

		IntVar[][] plPiE = { pl1PiE, pl2PiE, pl3PiE, pl4PiE, pl5PiE };

		// Calculating the minimal waiting time:
		// 1. minimize over song origins for each player
		// 2. maximize over song endings for each player
		// 3. calculate total duration for each player (durTot = maxE - minO <=>
		// maxE = durTot + minO)
		// 4. calculate waiting time for each player (durWait = durTot - durPlay
		// <=> durTot = durWait + durPlay
		// 5. summarize over waiting times for players
		// 6. impose sum of waiting times as cost

		IntVar[] minPlOs = new IntVar[5];
		IntVar[] maxPlEs = new IntVar[5];
		IntVar[] durTots = new IntVar[5];
		IntVar[] durPlays = new IntVar[5];
		IntVar[] waits = new IntVar[5];

		for (int i = 0; i < 5; i++) {
			/*
			 * Creating necessary variables
			 */
			minPlOs[i] = new IntVar(store, "min origins player" + (i + 1), 0,
					sumDurations);
			maxPlEs[i] = new IntVar(store, "max origins player" + (i + 1), 0,
					sumDurations);
			durTots[i] = new IntVar(store, "duration total player" + (i + 1),
					0, sumDurations);
			durPlays[i] = new IntVar(store, "duration played player" + (i + 1),
					0, sumDurations);
			waits[i] = new IntVar(store, "duration waited player" + (i + 1), 0,
					sumDurations);
			store.impose(new Min(plPiO[i], minPlOs[i])); // 1
			store.impose(new Max(plPiE[i], maxPlEs[i])); // 2
			store.impose(new XplusYeqZ(durTots[i], minPlOs[i], maxPlEs[i])); // 3
			store.impose(new Sum(plPiD[i], durPlays[i])); // 4
			store.impose(new XplusYeqZ(durPlays[i], waits[i], durTots[i]));
		}

		IntVar sumWaits = new IntVar(store, "Sum of waiting times", 0, 49); // 5
		store.impose(new Sum(waits, sumWaits));

		Search<IntVar> label = new DepthFirstSearch<IntVar>();

		SelectChoicePoint<IntVar> select = new SimpleSelect<IntVar>(piO,
				new SmallestDomain<IntVar>(), new SmallestMax<IntVar>(),
				new IndomainMax<IntVar>());
		System.out.println("Starting search...");
		boolean Result = label.labeling(store, select, sumWaits); // 6

		if (Result) {
			System.out.println("Minimum time for schedule is "
					+ sumWaits.value());
		} else {
			System.out.println("\n*** No");
		}

	}

}
